package com.fanxuankai.boot.redis.repository;

import com.alibaba.fastjson.JSON;
import com.fanxuankai.boot.redis.annotation.RedisHash;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.lang.NonNull;
import org.springframework.util.CollectionUtils;

import java.util.*;
import java.util.stream.Collectors;

/**
 * RedisRepository 实现类
 *
 * @author fanxuankai
 */
public class SimpleRedisRepository<T, ID> implements RedisRepository<T, ID> {

    private final RedisHash redisHash;
    private final Class<T> domainType;
    private final RedisTemplate<Object, Object> redisTemplate;

    /**
     * 构造器
     *
     * @param domainType    the domain type
     * @param redisTemplate the RedisTemplate
     */
    public SimpleRedisRepository(Class<T> domainType, RedisTemplate<Object, Object> redisTemplate,
            Class<? extends T> redisRepositoryInterface) {
        this.domainType = domainType;
        this.redisTemplate = redisTemplate;
        this.redisHash = AnnotationUtils.findAnnotation(redisRepositoryInterface, RedisHash.class);
    }

    @Override
    public Optional<T> findById(ID id) {
        return Optional.ofNullable(id)
                .map(Object::toString)
                .flatMap(hashKey -> convert(redisTemplate.opsForHash()
                        .get(key(), hashKey)));
    }

    @Override
    public boolean existsById(ID id) {
        return findById(id).isPresent();
    }

    @Override
    public List<T> findAll() {
        return getAll(key());
    }

    @Override
    public List<T> findAllById(Iterable<ID> ids) {
        return multiGet(key(), Optional.ofNullable(ids)
                .map(idsIt -> {
                    Set<Object> idSet = new HashSet<>();
                    for (Object id : idsIt) {
                        if (id == null) {
                            continue;
                        }
                        idSet.add(id.toString());
                    }
                    return idSet;
                })
                .orElse(Collections.emptySet()));
    }

    @Override
    public long count() {
        return redisTemplate.opsForHash()
                .size(key());
    }

    @Override
    public T getOne(@NonNull ID id) {
        return findById(id).orElseThrow(NullPointerException::new);
    }

    private List<T> getAll(String key) {
        return Optional.ofNullable(key)
                .map(s -> convertList(redisTemplate.opsForHash()
                        .values(s)))
                .orElse(Collections.emptyList());
    }

    private List<T> multiGet(String key, Collection<Object> hashKeys) {
        if (key == null || CollectionUtils.isEmpty(hashKeys)) {
            return Collections.emptyList();
        }
        return convertList(redisTemplate.opsForHash()
                .multiGet(key, hashKeys));
    }

    private List<T> convertList(List<Object> objects) {
        objects = objects.stream()
                .filter(Objects::nonNull)
                .collect(Collectors.toList());
        if (CollectionUtils.isEmpty(objects)) {
            return Collections.emptyList();
        }
        return objects.stream()
                .map(o -> JSON.parseObject(JSON.toJSONString(o), domainType))
                .collect(Collectors.toList());
    }

    private Optional<T> convert(Object object) {
        return Optional.ofNullable(object)
                .map(o -> JSON.parseObject(JSON.toJSONString(o), domainType));
    }

    private String key() {
        return redisHash.value();
    }
}